package net.kehuilai.tools;

import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;

import java.util.*;

/**
 * Created by andy,genghz on 6/9/15.
 */
public class RedisTool {
    private static final Logger log = LoggerFactory.getLogger(RedisTool.class);

    private int db = 0;
    private int retry = 0;

    private ThreadSafeJedis safeJedis;

    public RedisTool(String host, int port, int db) {
        this.db = db > 0 ? db : 0;
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        this.safeJedis = new ThreadSafeJedis(poolConfig, host, port, db);
    }

    public int getDb() {
        return this.db;
    }

    public int getRetry() {
        return this.retry;
    }

    // --------------------------------------------- key - val ---------------------------------------------
    public RedisTool set(String key, String value) {
        return this.set(key, value, 0);
    }

    public RedisTool set(String key, String value, int seconds) {
        try {
            value = StringEscapeUtils.escapeJava(value);
            if (seconds > 0) {
                this.safeJedis.setex(key, seconds, value);
            } else {
                this.safeJedis.set(key, value);
            }
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return this;
    }

    public String get(String key) {
        String value = null;

        try {
            value = this.safeJedis.get(key);
            value = StringEscapeUtils.unescapeJava(value);
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return value;
    }

    public String getSet(String key, String value) {
        String valueOld = null;
        try {
            valueOld = this.safeJedis.getSet(key, value);
            valueOld = StringEscapeUtils.unescapeJava(valueOld);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return valueOld;
    }

    public Map<String, String> getAll(Iterable<? extends String> keys) {
        HashMap map = new HashMap();
        Iterator var3 = keys.iterator();

        while (var3.hasNext()) {
            String key = (String) var3.next();
            map.put(key, this.get(key));
        }

        return map;
    }

    public long del(String key) {
        long resp = 0L;

        try {
            resp = this.safeJedis.del(key);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return resp;
    }

    // --------------------------------------------- key opt ---------------------------------------------
    public boolean exists(String key) {
        boolean keyIsExist = false;

        try {
            keyIsExist = this.safeJedis.exists(key);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return keyIsExist;
    }

    public void expire(String key, int seconds) {
        this.safeJedis.expire(key, seconds);
    }

    // --------------------------------------------- hashes ---------------------------------------------
    public long hset(String key, String field, String value) {
        long result = 0L;

        try {
            value = StringEscapeUtils.escapeJava(value);
            result = this.safeJedis.hset(key, field, value);
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return result;
    }

    public String hget(String key, String field) {
        String value = null;

        try {
            value = StringEscapeUtils.unescapeJava(this.safeJedis.hget(key, field));
        } catch (JedisException var5) {
            var5.printStackTrace();
        }

        return value;
    }

    public long hdel(String key, String... fields) {
        long resp = 0;
        try {
            resp = this.safeJedis.hdel(key, fields);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return resp;
    }

    public Map<String, String> hgetall(String key) {
        Map<String, String> result = new HashMap<String, String>();
        try {
            Map<String, String> map = safeJedis.hgetAll(key);
            for (String field : map.keySet()) {
                result.put(field, StringEscapeUtils.unescapeJava(map.get(field)));
            }
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // --------------------------------------------- 累加器 ---------------------------------------------
    // key - val++
    public long incr(String key) {
        long result = 0L;

        try {
            result = this.safeJedis.incr(key);
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return result;
    }

    // key - val + amount
    public long incrByFailOnZero(String key, long amount) {
        long result = 0L;

        try {
            result = this.safeJedis.incrByFailOnZero(key, amount);
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return result;
    }

    // key - val--
    public long decr(String key) {
        long result = 0L;

        try {
            result = this.safeJedis.decr(key);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // hashes
    public long hincrBy(String key, String field, long amount) {
        long result = 0L;

        try {
            result = this.safeJedis.hincrBy(key, field, amount);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // hashes
    public long hincrByFailOnZero(String key, String field, long amount) {
        long result = 0L;

        try {
            result = this.safeJedis.hincrByFailOnZero(key, field, amount);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // hashes
    public long dailyIncr(String key) {
        long result = 0L;

        try {
            result = this.safeJedis.incr(key);
            if (result <= 1L) {
                long e = DateTool.getTomorrowTime();
                this.safeJedis.expireAt(key, e / 1000L);
            }
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return result;
    }

    // hashes
    public long dailyDecr(String key) {
        long result = 0L;

        try {
            result = this.safeJedis.decr(key);
            if (result < 1L) {
                this.safeJedis.set(key, "0");
            }
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // --------------------------------------------- set 集合 ---------------------------------------------
    public void sadd(String key, String... vals) {
        try {
            this.safeJedis.sadd(key, vals);
        } catch (JedisException e) {
            e.printStackTrace();
        }
    }

    public Set<String> smembers(String key) {
        Set<String> vals = null;
        try {
            vals = this.safeJedis.smembers(key);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return vals;
    }

    // --------------------------------------------- list 列表 ---------------------------------------------
    public long rpush(String key, String... vals) {
        long result = 0L;

        try {
            result = this.safeJedis.rpush(key, vals);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    public List<String> lrange(String key) {
        List<String> result = null;
        try {
            result = this.safeJedis.lrange(key, 0, -1);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    // --------------------------------------------- sub/pub ---------------------------------------------
    public long publish(String channel, String message) {
        long result = 0L;

        try {
            result = this.safeJedis.publish(channel, message);
        } catch (JedisException e) {
            e.printStackTrace();
        }

        return result;
    }

    public void doSubscribe(String[] channels, JedisPubSub jedisPubSub) {
        if (channels != null && channels.length > 0) {
            Jedis client = this.safeJedis.getClient();
            client.subscribe(jedisPubSub, channels);
        }
    }

    public void doTransaction(String[] lockKeys, RedisTransactionCallback callback, Object context, int retryTimes) {

        boolean retry = false;
        boolean skip = false;
        Jedis client = safeJedis.getClient();


        try {
            callback.beforeTransaction(client, context);
        } catch (Exception e) {
            log.warn("beforeTransaction failed: {}, {}", DebugTool.getCallerName(e), e.getMessage());
            callback.afterTransaction(client, context);
            client.close();
            retry = true;
            skip = true;
        }

        // 加锁
        if (lockKeys != null && lockKeys.length > 0)
            client.watch(lockKeys);

        if (!skip) {
            // 事务开始
            Transaction transaction = client.multi();

            try {

                callback.run(transaction, context);

                // 处理结果
                List<Object> result = transaction.exec();

                if (result == null) {
                    callback.onFailed(TransactionStatus.WATCH_FAILED,
                            context,
                            new RuntimeException("failed on duplicate request"));

                    retry = true;

                } else {
                    callback.onSuccess(TransactionStatus.SUCCESS, context, result);
                }

            } catch (Exception e) {

                log.error("redis transaction exception, {}: {}", retryTimes, e.getMessage());

                try {
                    transaction.discard();
                } catch (Exception ignore) {
                }

                callback.onFailed(TransactionStatus.FAILED, context, e);

                retry = true;

            } finally {

                callback.afterTransaction(client, context);

                // 解锁, 事务结束
                client.close();
            }
        }

        // 重试
        if (retry && retryTimes > 0) {
            doTransaction(lockKeys, callback, context, --retryTimes);
        }
    }

    // --------------------------------------------- base ---------------------------------------------
    public Jedis getClient() {
        return this.safeJedis.getClient();
    }

    public void destroy() {
        this.safeJedis.close();
    }
}
